package handler

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"github.com/apache/rocketmq-client-go/v2/consumer"
	"github.com/apache/rocketmq-client-go/v2/primitive"
	"go.uber.org/zap"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
	"google.golang.org/protobuf/types/known/emptypb"
	"gorm.io/gorm"
	"mxshop_srvs/inventory_srv/global"
	"mxshop_srvs/inventory_srv/model"
	"mxshop_srvs/inventory_srv/proto"
	"sync"
)

type InventoryServer struct {
	proto.UnimplementedInventoryServer
}

var mtx sync.Mutex

// SetInv 设置库存 与 更新库存
func (InventoryServer) SetInv(ctx context.Context, req *proto.GoodsInvInfo) (*emptypb.Empty, error) {
	var inv model.Inventory

	// 检查数据库连接是否为 nil
	if global.DB == nil {
		return nil, fmt.Errorf("database connection is not initialized")
	}

	// 查找库存记录
	result := global.DB.Where("goods", req.GoodsId).First(&inv)
	if result.Error != nil {
		if errors.Is(result.Error, gorm.ErrRecordNotFound) {
			// 如果记录不存在，则初始化一个新的库存记录
			inv.Goods = req.GoodsId
			inv.Stocks = req.Num
		} else {
			// 记录其他错误
			return nil, fmt.Errorf("failed to fetch inventory: %v", result.Error)
		}
	} else {
		// 更新库存信息
		inv.Stocks = req.Num
	}

	// 保存库存记录
	if err := global.DB.Save(&inv).Error; err != nil {
		return nil, fmt.Errorf("failed to save inventory: %v", err)
	}

	return &emptypb.Empty{}, nil
}

// InvDetail 获取库存详情
func (InventoryServer) InvDetail(ctx context.Context, req *proto.GoodsInvInfo) (*proto.GoodsInvInfo, error) {

	var inv model.Inventory

	if result := global.DB.Where("goods", req.GoodsId).First(&inv); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "没有库存信息")
	}
	return &proto.GoodsInvInfo{
		GoodsId: inv.Goods,
		Num:     inv.Stocks,
	}, nil
}

//// acquireLock 获取锁
//func acquireLock(ctx context.Context, key string, ttl time.Duration) (*redislock.Lock, error) {
//	lock, err := global.LockClient.Obtain(ctx, key, ttl, nil)
//	if errors.Is(err, redislock.ErrNotObtained) {
//		return nil, nil // 锁未获取成功
//	}
//	return lock, err
//}
//
//// releaseLock 释放锁
//func releaseLock(lock *redislock.Lock) error {
//	if lock == nil {
//		return fmt.Errorf("锁未初始化")
//	}
//	return lock.Release(context.Background())
//}

// Sell 库存扣减
func (InventoryServer) Sell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	//本地事务 [1:10,2:5,3:20]  全部成功/全部失败 必须保证事务的一致性
	//查询可以不使用tx 局部变量.写入/更新操作则需要 使用启动事务后的 tx
	//并发情况下 可能会出现超卖
	tx := global.DB.Begin()
	sellDetail := model.StockSellDetail{
		OrderSn: req.OrderSn,
		Status:  1,
	}
	var details []model.GoodsDetail
	for _, goodInfo := range req.GoodsInfo { //req.GoodsInfo 商品列表,切片
		details = append(details, model.GoodsDetail{
			Goods: goodInfo.GoodsId,
			Num:   goodInfo.Num,
		})
		var inv model.Inventory
		mutex := global.RedSync.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
		if err := mutex.Lock(); err != nil {
			return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
		}
		if result := global.DB.Where("goods", goodInfo.GoodsId).First(&inv); result.RowsAffected == 0 {
			tx.Rollback()                                              // 事务回滚
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息") //参数错误
		}
		//判断库存是否充足
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() //事务回滚
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		//扣减库存
		inv.Stocks -= goodInfo.Num
		//更新库存 set stocks = stocks - 1 , version = version + 1 where goods = goods and version = version
		// 零值 对于 int - 0 , 这种会被gorm忽略掉

		if ok, err := mutex.Unlock(); !ok || err != nil {
			return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
		}
	}
	sellDetail.Detail = details
	//写入selldetail表
	if rs := tx.Create(&sellDetail); rs.RowsAffected == 0 {
		tx.Rollback()
		return nil, status.Errorf(codes.Internal, "保存库存扣减历史失败")
	}
	//并发扣减,会出现数据不一致问题 -  锁,分布式锁
	tx.Commit() //手动提交事务
	return &emptypb.Empty{}, nil
}

// ReBack 库存归还
func (InventoryServer) ReBack(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {

	//1. 订单超时归还 2.订单创建失败,归还之前扣减库存 (必须) 3. 手动归还
	tx := global.DB.Begin()
	mtx.Lock()                               //获取锁
	for _, goodInfo := range req.GoodsInfo { //req.GoodsInfo 商品列表,切片
		var inv model.Inventory
		if result := global.DB.Where("goods", goodInfo.GoodsId).First(&inv); result.RowsAffected == 0 {
			tx.Rollback()                                              // 事务回滚
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息") //参数错误
		}
		//归还库存,会出现数据不一致问题 -  锁,分布式锁
		inv.Stocks += goodInfo.Num
		tx.Save(&inv)
	}
	tx.Commit()  //手动提交事务
	mtx.Unlock() //释放锁
	return &emptypb.Empty{}, nil
}

//以下代码都是TCC分布式事务案例,并非实装

// TrySell 库存扣减冻结
func (InventoryServer) TrySell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	//本地事务 [1:10,2:5,3:20]  全部成功/全部失败 必须保证事务的一致性
	//查询可以不使用tx 局部变量.写入/更新操作则需要 使用启动事务后的 tx
	//并发情况下 可能会出现超卖
	tx := global.DB.Begin()
	for _, goodInfo := range req.GoodsInfo { //req.GoodsInfo 商品列表,切片
		var inv model.InventoryNew
		mutex := global.RedSync.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
		if err := mutex.Lock(); err != nil {
			return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
		}
		if result := global.DB.Where("goods", goodInfo.GoodsId).First(&inv); result.RowsAffected == 0 {
			tx.Rollback()                                              // 事务回滚
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息") //参数错误
		}
		//判断库存是否充足
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() //事务回滚
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		//扣减库存
		//inv.Stocks -= goodInfo.Num
		//冻结库存
		inv.Freeze += goodInfo.Num
		//更新库存 set stocks = stocks - 1 , version = version + 1 where goods = goods and version = version
		// 零值 对于 int - 0 , 这种会被gorm忽略掉

		if ok, err := mutex.Unlock(); !ok || err != nil {
			return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
		}
	}
	//并发扣减,会出现数据不一致问题 -  锁,分布式锁
	tx.Commit() //手动提交事务
	return &emptypb.Empty{}, nil
}

// TryInvDetail 冻结库存后返回库存的信息
func (InventoryServer) TryInvDetail(ctx context.Context, req *proto.GoodsInvInfo) (*proto.GoodsInvInfo, error) {

	var inv model.InventoryNew

	if result := global.DB.Where("goods", req.GoodsId).First(&inv); result.RowsAffected == 0 {
		return nil, status.Errorf(codes.NotFound, "没有库存信息")
	}
	return &proto.GoodsInvInfo{
		GoodsId: inv.Goods,
		Num:     inv.Stocks - inv.Freeze,
	}, nil
}

// ComfirmSell 确认库存扣减
func (InventoryServer) ComfirmSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	//本地事务 [1:10,2:5,3:20]  全部成功/全部失败 必须保证事务的一致性
	//查询可以不使用tx 局部变量.写入/更新操作则需要 使用启动事务后的 tx
	//并发情况下 可能会出现超卖
	tx := global.DB.Begin()

	for _, goodInfo := range req.GoodsInfo { //req.GoodsInfo 商品列表,切片
		var inv model.InventoryNew
		mutex := global.RedSync.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
		if err := mutex.Lock(); err != nil {
			return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
		}
		if result := global.DB.Where("goods", goodInfo.GoodsId).First(&inv); result.RowsAffected == 0 {
			tx.Rollback()                                              // 事务回滚
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息") //参数错误
		}
		//判断库存是否充足
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() //事务回滚
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		//扣减库存
		inv.Stocks -= goodInfo.Num
		//冻结库存
		inv.Freeze -= goodInfo.Num
		//更新库存 set stocks = stocks - 1 , version = version + 1 where goods = goods and version = version
		// 零值 对于 int - 0 , 这种会被gorm忽略掉

		if ok, err := mutex.Unlock(); !ok || err != nil {
			return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
		}
	}
	//并发扣减,会出现数据不一致问题 -  锁,分布式锁
	tx.Commit() //手动提交事务
	return &emptypb.Empty{}, nil
}

// CancelSell 取消库存冻结
func (InventoryServer) CancelSell(ctx context.Context, req *proto.SellInfo) (*emptypb.Empty, error) {
	//本地事务 [1:10,2:5,3:20]  全部成功/全部失败 必须保证事务的一致性
	//查询可以不使用tx 局部变量.写入/更新操作则需要 使用启动事务后的 tx
	//并发情况下 可能会出现超卖
	tx := global.DB.Begin()

	for _, goodInfo := range req.GoodsInfo { //req.GoodsInfo 商品列表,切片
		var inv model.InventoryNew
		mutex := global.RedSync.NewMutex(fmt.Sprintf("goods_%d", goodInfo.GoodsId))
		if err := mutex.Lock(); err != nil {
			return nil, status.Errorf(codes.Internal, "获取redis分布式锁异常")
		}
		if result := global.DB.Where("goods", goodInfo.GoodsId).First(&inv); result.RowsAffected == 0 {
			tx.Rollback()                                              // 事务回滚
			return nil, status.Errorf(codes.InvalidArgument, "没有库存信息") //参数错误
		}
		//判断库存是否充足
		if inv.Stocks < goodInfo.Num {
			tx.Rollback() //事务回滚
			return nil, status.Errorf(codes.ResourceExhausted, "库存不足")
		}
		//冻结库存
		inv.Freeze -= goodInfo.Num
		//更新库存 set stocks = stocks - 1 , version = version + 1 where goods = goods and version = version
		// 零值 对于 int - 0 , 这种会被gorm忽略掉

		if ok, err := mutex.Unlock(); !ok || err != nil {
			return nil, status.Errorf(codes.Internal, "释放redis分布式锁异常")
		}
	}
	//并发扣减,会出现数据不一致问题 -  锁,分布式锁
	tx.Commit() //手动提交事务
	return &emptypb.Empty{}, nil
}

func AutoReback(ctx context.Context, msgs ...*primitive.MessageExt) (consumer.ConsumeResult, error) {
	type OrderInfo struct {
		OrderSn string
	}
	for i := range msgs {
		//归还库存,应具体知道每件商品该归还多少   重复归还问题
		//这个接口应该确保幂等性,不能因为一个消息的重复发送导致订单的库存归还多次,没有扣减库存不要归还
		//如何确保呢? 建一张表,这张表记录详细订单扣减细节,以及归还细节
		var orderInfo OrderInfo
		if err := json.Unmarshal(msgs[i].Body, &orderInfo); err != nil {
			zap.S().Errorf("解析json失败:%v\n", msgs[i].Body)
			return consumer.ConsumeSuccess, nil
		}
		//去将inv的库存加回去 将selldetail的status设置为2 ,要在事务中进行
		tx := global.DB.Begin()
		var sellDetail model.StockSellDetail
		if rs := tx.Model(&model.StockSellDetail{}).Where(&model.StockSellDetail{OrderSn: orderInfo.OrderSn, Status: 1}).First(&sellDetail); rs.RowsAffected == 0 {
			return consumer.ConsumeSuccess, nil
		}
		//如果查询到,那么逐个归还
		for _, orderGood := range sellDetail.Detail {
			//查询库存表 update语句的 update xx set stocks=stocks+2
			if res := tx.Model(&model.Inventory{}).Where(&model.Inventory{
				Goods: orderGood.Goods,
			}).Update("stocks", gorm.Expr("stocks+?", orderGood.Num)); res.RowsAffected == 0 {
				tx.Rollback()
				return consumer.ConsumeRetryLater, nil
			}
		}
		sellDetail.Status = 2
		if res := tx.Model(&model.StockSellDetail{}).Where(&model.StockSellDetail{
			OrderSn: orderInfo.OrderSn,
		}).Update("status", 2); res.RowsAffected == 0 {
			tx.Rollback()
			return consumer.ConsumeRetryLater, nil
		}
		tx.Commit()
		return consumer.ConsumeSuccess, nil
	}
	return consumer.ConsumeSuccess, nil
}
